using Beaker.OpenCube.Render.Maths;
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace Beaker.OpenCube.Render.Sampling
{
	/// <summary>
	/// Base that you can use for implementing an ISampler
	/// </summary>
	public abstract class Sampler
		: ISampler
	{
		protected Sampler( int sampleCount )
		{
			Contract.Requires<ArgumentOutOfRangeException>( sampleCount >= 1 );
			_sampleCount = sampleCount;
		}

		private readonly Random _random = new Random();

		[Pure]
		protected Random Random
		{

			get { return _random; }
		}

		private readonly int _sampleCount;

		/// <summary>
		/// Gets the number of samples
		/// </summary>
		[Pure]
		public int SampleCount
		{

			get { return _sampleCount; }
		}

		/// <summary>
		/// Generate samples.
		/// </summary>
		/// <returns>A sequence of samples.</returns>
		public abstract IEnumerable<Point2> GenerateSamplesSquare( SampleOptions options = SampleOptions.None );

		/// <summary>
		/// Generate samples in a disc form
		/// </summary>
		/// <param name="options"></param>
		/// <returns>A sequence of samples</returns>
		public virtual IEnumerable<Point2> GenerateSamplesDisc( SampleOptions options = SampleOptions.None )
		{
			foreach( Point2 point in GenerateSamplesSquare() )
			{
				double x = point.X * 2d - 1d;
				double y = point.Y * 2d - 1d;
				double r;
				double phi;

				if( x > -y )
				{
					if( x > y )
					{
						r = x;
						phi = y / x;
					}
					else
					{
						r = y;
						phi = 2d - x / y;
					}
				}
				else
				{
					if( x < y )
					{
						r = -x;
						phi = 4d * y / x;
					}
					else
					{
						r = -y;
						if( y != 0d )
							phi = 6d - x / y;
						else
						{
							phi = 0d;
						}
					}
				}

				phi *= Math.PI / 4d;

				yield return
					new Point2(
						r * Math.Cos( phi ),
						r * Math.Sin( phi )
					);
			}
		}
	}
}